//------------------------------------------------------------------//
//		KEYBOARD DRIVER												//
//		-Janvdl														//
//																	//
//		Coded: 06/12/2009											//
//																	//
//		Edit log: None												//
//																	//
//																	//
//		Notes/changes:												//
//			1. Num lock ON by default in kkybrd_install () - Janvdl	//
//------------------------------------------------------------------//

//Header files

#include <string.h>
#include <ctype.h>
#include <hal.h>
#include "keyboard.h"

//Private definitions, enums

enum KYBRD_ENCODER_IO
{
	KYBRD_ENC_INPUT_BUF  = 0x60,
	KYBRD_ENC_CMD_REG    = 0x60
};

enum KYBRD_CTRL_IO
{
	KYBRD_CTRL_STATS_REG = 0x64,
	KYBRD_CTRL_CMD_REG	 = 0x64
};

enum KYBRD_ENC_CMDS {

	KYBRD_ENC_CMD_SET_LED				=	0xED,
	KYBRD_ENC_CMD_ECHO					=	0xEE,
	KYBRD_ENC_CMD_SCAN_CODE_SET			=	0xF0,
	KYBRD_ENC_CMD_ID					=	0xF2,
	KYBRD_ENC_CMD_AUTODELAY				=	0xF3,
	KYBRD_ENC_CMD_ENABLE				=	0xF4,
	KYBRD_ENC_CMD_RESETWAIT				=	0xF5,
	KYBRD_ENC_CMD_RESETSCAN				=	0xF6,
	KYBRD_ENC_CMD_ALL_AUTO				=	0xF7,
	KYBRD_ENC_CMD_ALL_MAKEBREAK			=	0xF8,
	KYBRD_ENC_CMD_ALL_MAKEONLY			=	0xF9,
	KYBRD_ENC_CMD_ALL_MAKEBREAK_AUTO	=	0xFA,
	KYBRD_ENC_CMD_SINGLE_AUTOREPEAT		=	0xFB,
	KYBRD_ENC_CMD_SINGLE_MAKEBREAK		=	0xFC,
	KYBRD_ENC_CMD_SINGLE_BREAKONLY		=	0xFD,
	KYBRD_ENC_CMD_RESEND				=	0xFE,
	KYBRD_ENC_CMD_RESET					=	0xFF
};

enum KYBRD_CTRL_STATS_MASK {

	KYBRD_CTRL_STATS_MASK_OUT_BUF	=	1,		//00000001
	KYBRD_CTRL_STATS_MASK_IN_BUF	=	2,		//00000010
	KYBRD_CTRL_STATS_MASK_SYSTEM	=	4,		//00000100
	KYBRD_CTRL_STATS_MASK_CMD_DATA	=	8,		//00001000
	KYBRD_CTRL_STATS_MASK_LOCKED	=	0x10,	//00010000
	KYBRD_CTRL_STATS_MASK_AUX_BUF	=	0x20,	//00100000
	KYBRD_CTRL_STATS_MASK_TIMEOUT	=	0x40,	//01000000
	KYBRD_CTRL_STATS_MASK_PARITY	=	0x80	//10000000
};

enum KYBRD_CTRL_CMDS {

	KYBRD_CTRL_CMD_READ				=	0x20,
	KYBRD_CTRL_CMD_WRITE			=	0x60,
	KYBRD_CTRL_CMD_SELF_TEST		=	0xAA,
	KYBRD_CTRL_CMD_INTERFACE_TEST	=	0xAB,
	KYBRD_CTRL_CMD_DISABLE			=	0xAD,
	KYBRD_CTRL_CMD_ENABLE			=	0xAE,
	KYBRD_CTRL_CMD_READ_IN_PORT		=	0xC0,
	KYBRD_CTRL_CMD_READ_OUT_PORT	=	0xD0,
	KYBRD_CTRL_CMD_WRITE_OUT_PORT	=	0xD1,
	KYBRD_CTRL_CMD_READ_TEST_INPUTS	=	0xE0,
	KYBRD_CTRL_CMD_SYSTEM_RESET		=	0xFE,
	KYBRD_CTRL_CMD_MOUSE_DISABLE	=	0xA7,
	KYBRD_CTRL_CMD_MOUSE_ENABLE		=	0xA8,
	KYBRD_CTRL_CMD_MOUSE_PORT_TEST	=	0xA9,
	KYBRD_CTRL_CMD_MOUSE_WRITE		=	0xD4
};

//Error codes

enum KYBRD_ERROR {

	KYBRD_ERR_BUF_OVERRUN			=	0,
	KYBRD_ERR_ID_RET				=	0x83AB,
	KYBRD_ERR_BAT					=	0xAA,
	KYBRD_ERR_ECHO_RET				=	0xEE,
	KYBRD_ERR_ACK					=	0xFA,
	KYBRD_ERR_BAT_FAILED			=	0xFC,
	KYBRD_ERR_DIAG_FAILED			=	0xFD,
	KYBRD_ERR_RESEND_CMD			=	0xFE,
	KYBRD_ERR_KEY					=	0xFF
};

//Private data members

//Current scan code
static char _scancode;

//Lock keys
static bool _numlock, _capslock, _scrolllock;

//Shift, alt, ctrl - State
static bool _shift, _alt, _ctrl;

//Keyboard error
static int _kkybrd_error = 0;

//Basic assurance test
static bool _kkybrd_bat_res = false;

//Diagnostics test
static bool _kkybrd_diag_res = false;

//Resend command
static bool _kkybrd_resend_res = false;

//Keyboard disabled
static bool _kkybrd_disable = false;

//Scan code set
static int _kkybrd_scancode_std [] = {

	//! key			scancode
	KEY_UNKNOWN,	//0
	KEY_ESCAPE,		//1
	KEY_1,			//2
	KEY_2,			//3
	KEY_3,			//4
	KEY_4,			//5
	KEY_5,			//6
	KEY_6,			//7
	KEY_7,			//8
	KEY_8,			//9
	KEY_9,			//0xa
	KEY_0,			//0xb
	KEY_MINUS,		//0xc
	KEY_EQUAL,		//0xd
	KEY_BACKSPACE,	//0xe
	KEY_TAB,		//0xf
	KEY_Q,			//0x10
	KEY_W,			//0x11
	KEY_E,			//0x12
	KEY_R,			//0x13
	KEY_T,			//0x14
	KEY_Y,			//0x15
	KEY_U,			//0x16
	KEY_I,			//0x17
	KEY_O,			//0x18
	KEY_P,			//0x19
	KEY_LEFTBRACKET,//0x1a
	KEY_RIGHTBRACKET,//0x1b
	KEY_RETURN,		//0x1c
	KEY_LCTRL,		//0x1d
	KEY_A,			//0x1e
	KEY_S,			//0x1f
	KEY_D,			//0x20
	KEY_F,			//0x21
	KEY_G,			//0x22
	KEY_H,			//0x23
	KEY_J,			//0x24
	KEY_K,			//0x25
	KEY_L,			//0x26
	KEY_SEMICOLON,	//0x27
	KEY_QUOTE,		//0x28
	KEY_GRAVE,		//0x29
	KEY_LSHIFT,		//0x2a
	KEY_BACKSLASH,	//0x2b
	KEY_Z,			//0x2c
	KEY_X,			//0x2d
	KEY_C,			//0x2e
	KEY_V,			//0x2f
	KEY_B,			//0x30
	KEY_N,			//0x31
	KEY_M,			//0x32
	KEY_COMMA,		//0x33
	KEY_DOT,		//0x34
	KEY_SLASH,		//0x35
	KEY_RSHIFT,		//0x36
	KEY_KP_ASTERISK,//0x37
	KEY_RALT,		//0x38
	KEY_SPACE,		//0x39
	KEY_CAPSLOCK,	//0x3a
	KEY_F1,			//0x3b
	KEY_F2,			//0x3c
	KEY_F3,			//0x3d
	KEY_F4,			//0x3e
	KEY_F5,			//0x3f
	KEY_F6,			//0x40
	KEY_F7,			//0x41
	KEY_F8,			//0x42
	KEY_F9,			//0x43
	KEY_F10,		//0x44
	KEY_KP_NUMLOCK,	//0x45
	KEY_SCROLLLOCK,	//0x46
	KEY_HOME,		//0x47
	KEY_KP_8,		//0x48	//keypad up arrow
	KEY_PAGEUP,		//0x49
	KEY_KP_2,		//0x50	//keypad down arrow
	KEY_KP_3,		//0x51	//keypad page down
	KEY_KP_0,		//0x52	//keypad insert key
	KEY_KP_DECIMAL,	//0x53	//keypad delete key
	KEY_UNKNOWN,	//0x54
	KEY_UNKNOWN,	//0x55
	KEY_UNKNOWN,	//0x56
	KEY_F11,		//0x57
	KEY_F12			//0x58
};

//Invalid scan code
const int INVALID_SCANCODE = 0;


//Interface data
uint8_t		kybrd_ctrl_read_status ();
void		kybrd_ctrl_send_cmd (uint8_t);
uint8_t		kybrd_enc_read_buf ();
void		kybrd_enc_send_cmd (uint8_t);
void _cdecl i86_kybrd_irq ();

//Read keyboard controller status
uint8_t kybrd_ctrl_read_status()
{
	return inportb (KYBRD_CTRL_STATS_REG);
}

//Send command to keyboard controller
void kybrd_ctrl_send_cmd(uint8_t cmd)
{
	while (true)
	{
		if ((kybrd_ctrl_read_status () & KYBRD_CTRL_STATS_MASK_IN_BUF) == 0)
		{
			break; //Break the while
		}
	}
	outportb (KYBRD_CTRL_CMD_REG, cmd);
}

//Read encoder buffer
uint8_t kybrd_enc_read_buf ()
{
	return inportb (KYBRD_ENC_INPUT_BUF);
}

//Send command byte to keyboard encoder
void kybrd_enc_send_cmd (uint8_t cmd)
{
	while (true)
	{
		if ((kybrd_ctrl_read_status () & KYBRD_CTRL_STATS_MASK_IN_BUF) == 0)
		{
			break; //Break the while
		}
	}
	outportb (KYBRD_ENC_CMD_REG, cmd);
}

//Keyboard interrupt handler
void _cdecl i86_kybrd_irq ()
{
	_asm add esp, 12
	_asm pushad
	_asm cli

	static bool _extended = false;

	int code = 0;

	//Read scan code only if keyboard controller buffer is full
	if (kybrd_ctrl_read_status () & KYBRD_CTRL_STATS_MASK_OUT_BUF)
	{
		code = kybrd_enc_read_buf();

		if (code == 0xE0 || code == 0xE1)
		{
			//It's an extended code
			_extended = true;
		}
		else
		{
			_extended = false;

			if (code & 0x80) {	//Test bit 7

				//Convert the break code into its make code equivelant
				code -= 0x80;

				//Grab the key
				int key = _kkybrd_scancode_std [code];

				//Test if a special key has been released & set it
				switch (key) {

					case KEY_LCTRL:
					case KEY_RCTRL:
						_ctrl = false;
						break;

					case KEY_LSHIFT:
					case KEY_RSHIFT:
						_shift = false;
						break;

					case KEY_LALT:
					case KEY_RALT:
						_alt = false;
						break;
				}
			}
			else 
			{
				//Set the scan code
				_scancode = code;

				//Grab the key
				int key = _kkybrd_scancode_std [code];

				//Test if user is holding down any special keys & set it
				switch (key) {

					case KEY_LCTRL:
					case KEY_RCTRL:
						_ctrl = true;
						break;

					case KEY_LSHIFT:
					case KEY_RSHIFT:
						_shift = true;
						break;

					case KEY_LALT:
					case KEY_RALT:
						_alt = true;
						break;

					case KEY_CAPSLOCK:
						_capslock = (_capslock) ? false : true;
						kkybrd_set_leds (_numlock, _capslock, _scrolllock);
						break;

					case KEY_KP_NUMLOCK:
						_numlock = (_numlock) ? false : true;
						kkybrd_set_leds (_numlock, _capslock, _scrolllock);
						break;

					case KEY_SCROLLLOCK:
						_scrolllock = (_scrolllock) ? false : true;
						kkybrd_set_leds (_numlock, _capslock, _scrolllock);
						break;
				}
			}
		}

		switch (code) 
		{

			case KYBRD_ERR_BAT_FAILED:
				_kkybrd_bat_res = false;
				break;

			case KYBRD_ERR_DIAG_FAILED:
				_kkybrd_diag_res = false;
				break;

			case KYBRD_ERR_RESEND_CMD:
				_kkybrd_resend_res = true;
				break;
		}		
	}

	//Let HAL know we're done
	interruptdone(0);

	//Return from interrupt handler
	_asm sti
	_asm popad
	_asm iretd
}

//Scroll lock state
bool kkybrd_get_scroll_lock()
{
	return _scrolllock;
}

//Num lock state
bool kkybrd_get_numlock()
{
	return _numlock;
}

//Caps lock state
bool kkybrd_get_capslock()
{
	return _capslock;
}

//Ctrl key state
bool kkybrd_get_ctrl ()	
{
	return _ctrl;
}

//Alt key state
bool kkybrd_get_alt () 
{
	return _alt;
}

//Shift key state
bool kkybrd_get_shift ()	
{
	return _shift;
}

//Ignore last resend request
void kkybrd_ignore_resend ()	
{
	_kkybrd_resend_res = false;
}

//Redo last command
bool kkybrd_check_resend () 
{
	return _kkybrd_resend_res;
}

//Diag test result
bool kkybrd_get_diagnostic_res () 
{
	return _kkybrd_diag_res;
}

//BAT test result
bool kkybrd_get_bat_res () 
{
	return _kkybrd_bat_res;
}

//Last scan code
uint8_t kkybrd_get_last_scan ()	
{
	return _scancode;
}

//Set LEDs
void kkybrd_set_leds (bool num, bool caps, bool, scroll)
{
	uint8_t data = 0;

	//Set or clear
	data = (scroll) ? (data | 1) : (data & 1);
	data = (num) ? (num | 2) : (num & 2);
	data = (caps) ? (num | 4) : (num & 4);

	//Send comman (update LEDs)
	kybrd_enc_send_cmd (KYBRD_ENC_CMD_SET_LED);
	kybrd_enc_send_cmd (data);
}

//Last key stroke
KEYCODE kkybrd_get_last_key () 
{
	return (_scancode!=INVALID_SCANCODE) ? ((KEYCODE)_kkybrd_scancode_std [_scancode]) : (KEY_UNKNOWN);
}

//Discard last scan
void kkybrd_discard_last_key () 
{
	_scancode = INVALID_SCANCODE;
}

//Convert key to an ASCII char
char kkybrd_key_to_ascii (KEYCODE code) 
{
	uint8_t key = code;

	//Ensure key is an ASCII char
	if (isascii (key)) 
	{
		//If shift key is down or caps lock is on, make the key uppercase
		if (_shift || _capslock)
		{
			if (key >= 'a' && key <= 'z')
			{
				key -= 32;
			}
		}

		if (_shift && !_capslock)
			if (key >= '0' && key <= '9')
				switch (key) {

					case '0':
						key = KEY_RIGHTPARENTHESIS;
						break;
					case '1':
						key = KEY_EXCLAMATION;
						break;
					case '2':
						key = KEY_AT;
						break;
					case '3':
						key = KEY_EXCLAMATION;
						break;
					case '4':
						key = KEY_HASH;
						break;
					case '5':
						key = KEY_PERCENT;
						break;
					case '6':
						key = KEY_CARRET;
						break;
					case '7':
						key = KEY_AMPERSAND;
						break;
					case '8':
						key = KEY_ASTERISK;
						break;
					case '9':
						key = KEY_LEFTPARENTHESIS;
						break;
				}
			else {

				switch (key) {
					case KEY_COMMA:
						key = KEY_LESS;
						break;

					case KEY_DOT:
						key = KEY_GREATER;
						break;

					case KEY_SLASH:
						key = KEY_QUESTION;
						break;

					case KEY_SEMICOLON:
						key = KEY_COLON;
						break;

					case KEY_QUOTE:
						key = KEY_QUOTEDOUBLE;
						break;

					case KEY_LEFTBRACKET :
						key = KEY_LEFTCURL;
						break;

					case KEY_RIGHTBRACKET :
						key = KEY_RIGHTCURL;
						break;

					case KEY_GRAVE:
						key = KEY_TILDE;
						break;

					case KEY_MINUS:
						key = KEY_UNDERSCORE;
						break;

					case KEY_PLUS:
						key = KEY_EQUAL;
						break;

					case KEY_BACKSLASH:
						key = KEY_BAR;
						break;
				}
			}

		//Return the key
		return key;
	}

	//Scan code != a valid ASCII char so no conversion is possible
	return 0;
}

//Disable the keyboard
void kkybrd_disable () 
{
	kybrd_ctrl_send_cmd (KYBRD_CTRL_CMD_DISABLE);
	_kkybrd_disable = true;
}

//Enable the keyboard
void kkybrd_enable () 
{
	kybrd_ctrl_send_cmd (KYBRD_CTRL_CMD_ENABLE);
	_kkybrd_disable = false;
}

//Keyboard disabled state
bool kkybrd_is_disabled () 
{
	return _kkybrd_disable;
}

//Reset the system
void kkybrd_reset_system () 
{
	//Writes 11111110 to the output port
	kybrd_ctrl_send_cmd (KYBRD_CTRL_CMD_WRITE_OUT_PORT);
	kybrd_enc_send_cmd (0xfe);
}

//Self test
bool kkybrd_self_test () 
{
	//Send command
	kybrd_ctrl_send_cmd (KYBRD_CTRL_CMD_SELF_TEST);

	//Wait for output buffer to be full
	while (true)
	{
		if (kybrd_ctrl_read_status () & KYBRD_CTRL_STATS_MASK_OUT_BUF)
		{
			break; //Break the while
		}
	}

	//If output buffer == 0x55, test passed
	return (kybrd_enc_read_buf () == 0x55) ? true : false;
}

//Prepare driver for use
void kkybrd_install (int irq) 
{
	//Install our interrupt handler (irq 1 uses interrupt 33)
	setvect (irq, i86_kybrd_irq);

	//Assume BAT test is good. If there is a problem, the IRQ handler will catch the error
	_kkybrd_bat_res = true;
	_scancode = 0;

	//Set lock keys and LEDs
	_numlock = _scrolllock = _capslock = false;
	kkybrd_set_leds (true, false, false);		//Numlock on by default!

	//Shift, ctrl, and alt keys
	_shift = _alt = _ctrl = false;
}

//END kybrd.cpp